package marmalius;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.List;

import marmalius.tools.Config;
import marmalius.tools.DBService;
import marmalius.tools.LogService;

public class PlayerHandler {
	private static PlayerHandler instance = new PlayerHandler();
	
	private int maxPlayers;
	private Player[] players;
	public static int playerSlotSearchStart = 1;
	public static String kickNick = "";
	public static String SYI = "";
	public static boolean kickAllPlayers = false;
	public static String messageToAll = "";
	public static int playerCount = 0;
	public static long serverLatencyStart = 0;
	public static long serverLatency = 0;
	public static long serverLatency1 = 0;
	public static long serverLatency2 = 0;
	public static long serverLatency3 = 0;
	public static String[] playersCurrentlyOn;
	public static boolean updateAnnounced;
	public static boolean updateRunning;
	public static int updateSeconds;
	public static long updateStartTime;
	private int lastchatid = 1;

	public PlayerHandler() {
		maxPlayers = Config.getInstance().getMaxPlayer();
		players = new Player[maxPlayers];
		playersCurrentlyOn = new String[maxPlayers];
		for (int i = 0; i < maxPlayers; i++) {
			players[i] = null;
		}
	}
	
	public int getNextChatID() {
		return lastchatid++;
	}

	public void newPlayerClient(java.net.Socket s, String connectedFrom) {
		int slot = -1, i = 1;
		do {
			if (players[i] == null) {
				slot = i;
				break;
			}
			else if (players[i].disconnected || players[i].playerName == null) {
				slot = i;
				break;
			}
			i++;
			if (i >= maxPlayers)
				i = 0;
		} while (i <= maxPlayers);

		client newClient = new client(s, slot);

		(new Thread(newClient)).start();
		players[slot] = newClient;
		players[slot].connectedFrom = connectedFrom;
		updatePlayerNames();
		playerSlotSearchStart = slot + 1;
		if (playerSlotSearchStart > maxPlayers)
			playerSlotSearchStart = 1;
	}

	public void destruct() {
		for (int i = 0; i < maxPlayers; i++) {
			if (players[i] == null)
				continue;
			players[i].destruct();
			players[i] = null;
		}
	}

	public static int getPlayerCount() {
		return playerCount;
	}

	public void updatePlayerNames() {
		playerCount = 0;
		for (int i = 0; i < maxPlayers; i++) {
			if (players[i] != null) {
				playersCurrentlyOn[i] = players[i].playerName;
				playerCount++;
			}
			else
				playersCurrentlyOn[i] = "";
		}
	}

	public boolean isPlayerOn(String playerName) {
		for (int i = 0; i < maxPlayers; i++) {
			if (playersCurrentlyOn[i] != null) {
				if (playersCurrentlyOn[i].equalsIgnoreCase(playerName))
					return true;
			}
		}
		return false;
	}

	public int getPlayerID(String playerName) {
		for (int i = 0; i < maxPlayers; i++) {
			if (playersCurrentlyOn[i] != null) {
				if (playersCurrentlyOn[i].equalsIgnoreCase(playerName))
					return i;
			}
		}
		return -1;
	}

	public void process(int time) {
		serverLatencyStart = System.currentTimeMillis();
		if (messageToAll.length() > 0) {
			int msgTo = 1;
			do {
				if (players[msgTo] != null)
					players[msgTo].globalMessage = messageToAll;
				msgTo++;
			} while (msgTo < maxPlayers);
			messageToAll = "";
		}
		if (kickAllPlayers == true) {
			int kickID = 1;
			do {
				if (players[kickID] != null)
					players[kickID].isKicked = true;
				kickID++;
			} while (kickID < maxPlayers);
			kickAllPlayers = false;
		}
		for (int i = 0; i < maxPlayers; i++) {
			if (players[i] == null || !players[i].isActive)
				continue;
			players[i].actionAmount--;
			
			players[i].preProcessing();
			while (players[i].process(time)) { }
			try {
				players[i].postProcessing();
				players[i].getNextPlayerMovement();
			} catch (Exception E) {}
			
			if (players[i].poisonTimer == 0) {
				players[i].poisonDmg = false;
				players[i].poisonDelay = -1;
			}
			if (players[i].poisonDelay > 0) {
				players[i].poisonDelay--;
			}
			if (players[i].poisonTimer > 0 && players[i].poisonDelay == 0) {}
					 
			if (players[i].disconnected) {
				for (int i2 = 0; i2 < NPCHandler.getInstance().getMaxNPC(); i2++) {
					if (NPCHandler.getInstance().getNPC(i2) != null && players[i] != null) {
						if (NPCHandler.getInstance().getNPC(i2).followPlayer == players[i].playerId && NPCHandler.getInstance().getNPC(i2) != null)
							NPCHandler.getInstance().getNPC(i2).IsDead = true;
					} 
				}
				if (players[i].tradeStatus >= 2)
					players[i].StopTradeScam();
				removePlayer(players[i]);
				players[i] = null;
			}
		}
		for (int i = 0; i < maxPlayers; i++) {
			if (players[i] == null || !players[i].isActive)
				continue;
			Calendar cal = new GregorianCalendar();
			int day = cal.get(Calendar.DAY_OF_MONTH);
			int month = cal.get(Calendar.MONTH);
			int year = cal.get(Calendar.YEAR);
			int calc = ((year * 10000) + (month * 100) + day);
			
			players[i].playerLastLogin = calc;
			if (players[i].disconnected) {
				for (int i3 = 0; i3 < NPCHandler.getInstance().getMaxNPC(); i3++) {
					if (NPCHandler.getInstance().getNPC(i3) != null && players[i] != null) {
						if (NPCHandler.getInstance().getNPC(i3).followPlayer == players[i].playerId)
							NPCHandler.getInstance().getNPC(i3).IsDead = true;
					}
				}
				removePlayer(players[i]);
				players[i] = null;
			}
			else {
				if (!players[i].initialized) {
					players[i].initialize();
					players[i].initialized = true;
				}
				else
					players[i].update();
			}
		}
				
		if (updateRunning && !updateAnnounced)
			updateAnnounced = true;
		
		if (updateRunning && System.currentTimeMillis() - updateStartTime > (updateSeconds * 1000)) {
			kickAllPlayers = true;
		}
		
		// post processing
		for (int i = 0; i < maxPlayers; i++) {
			if (players[i] == null || !players[i].isActive)
				continue;
			players[i].clearUpdateFlags();
		}
	}
	
	public void updateNPC(Player plr, stream str) {
		updateBlock.currentOffset = 0;
		str.createFrameVarSizeWord(65);
		str.initBitAccess();
		str.writeBits(8, plr.npcListSize);
		int size = plr.npcListSize;
		plr.npcListSize = 0;
		for (int i = 0; i < size; i++) {
			if (plr.RebuildNPCList == false && plr.withinDistance(plr.npcList[i]) == true) {
				plr.npcList[i].updateNPCMovement(str);
				plr.npcList[i].appendNPCUpdateBlock(updateBlock);
				plr.npcList[plr.npcListSize++] = plr.npcList[i];
			}
			else {
				int id = plr.npcList[i].npcId;
				plr.npcInListBitmap[id >> 3] &= ~(1 << (id & 7));
				str.writeBits(1, 1);
				str.writeBits(2, 3);
			}
		}
		for (int i = 0; i < NPCHandler.getInstance().getMaxNPC(); i++) {
			if (NPCHandler.getInstance().getNPC(i) != null) {
				int id = NPCHandler.getInstance().getNPC(i).npcId;
				if (plr.RebuildNPCList == false && (plr.npcInListBitmap[id >> 3] & (1 << (id & 7))) != 0) { }
				else if (plr.withinDistance(NPCHandler.getInstance().getNPC(i)) == false) { }
				else
					plr.addNewNPC(NPCHandler.getInstance().getNPC(i), str, updateBlock);
			}
		}
		plr.RebuildNPCList = false;
		if (updateBlock.currentOffset > 0) {
			str.writeBits(14, 16383);
			str.finishBitAccess();
			str.writeBytes(updateBlock.buffer, updateBlock.currentOffset, 0);
		}
		else
			str.finishBitAccess();
		str.endFrameVarSizeWord();
	}

	private static stream updateBlock = new stream(new byte[10000]);
	
	public void updatePlayer(Player plr, stream str) {
		updateBlock.currentOffset = 0;
		if (updateRunning && !updateAnnounced) {
			str.createFrame(114);
			str.writeWordBigEndian(updateSeconds * 50 / 30);
		}
		plr.updateThisPlayerMovement(str);
		boolean saveChatTextUpdate = plr.chatTextUpdateRequired;
		plr.chatTextUpdateRequired = false;
		plr.appendPlayerUpdateBlock(updateBlock);
		plr.chatTextUpdateRequired = saveChatTextUpdate;
		str.writeBits(8, plr.playerListSize);
		int size = plr.playerListSize;
		plr.playerListSize = 0;
		for (int i = 0; i < size; i++) {
			if (plr.playerList[i].didTeleport == false && plr.didTeleport == false && plr.withinDistance(plr.playerList[i]) == true) {
				plr.playerList[i].updatePlayerMovement(str);
				plr.playerList[i].appendPlayerUpdateBlock(updateBlock);
				plr.playerList[plr.playerListSize++] = plr.playerList[i];
			}
			else {
				int id = plr.playerList[i].playerId;
				plr.playerInListBitmap[id >> 3] &= ~(1 << (id & 7));
				str.writeBits(1, 1);
				str.writeBits(2, 3);
			}
		}
		for (int i = 0; i < maxPlayers; i++) {
			if (players[i] == null || players[i].isActive == false || players[i] == plr) {
			}
			else {
				int id = players[i].playerId;
				if (plr.didTeleport == false && (plr.playerInListBitmap[id >> 3] & (1 << (id & 7))) != 0) {
					
				}
				else if (plr.withinDistance(players[i]) == false) {
					
				}
				else {
					plr.addNewPlayer(players[i], str, updateBlock);
				}
			}
		}

		if (updateBlock.currentOffset > 0) {
			str.writeBits(11, 2047);
			str.finishBitAccess();
			str.writeBytes(updateBlock.buffer, updateBlock.currentOffset, 0);
		}
		else {
			str.finishBitAccess();
		}
		str.endFrameVarSizeWord();
	}

	private void removePlayer(Player plr) {
		try {
			if (plr.Privatechat != 2) {
				for (int i = 1; i < maxPlayers; i++) {
					if (players[i] == null || players[i].isActive == false)
						continue;
					try {
						players[i].pmupdate(plr.playerId, 0);
					}
					catch (Exception E) {}
				}
			}
			plr.destruct();
		} catch (Exception E) {}
	}

	public void println_debug(String str, int ID, String Name) {
		System.out.println("[client-" + ID + "-" + Name + "]: " + str);
	}
	
	public int getMaxPlayers () { return maxPlayers; }
    public Player getPlayer (int id) { return players[id]; }
    public Player[] getAllPlayer() { return players; }
    
    public void setPlayer(int index, Player player) { players[index] = player; }
    
    public void updateShowObj (int objId, int x, int y, int h, int type, boolean add) {
		
    	client xc = null;
		for (int i = 0; i < maxPlayers; i++) {
			if (players[i] == null)
				continue;
			if ((Math.abs(players[i].absX - x) <= 7 || Math.abs(players[i].absY - y) <= 7) && players[i].heightLevel == h) {
				xc = (client)players[i];
				if (add)
					xc.replaceObject(objId, x, y, 0, type);
				else {
					xc.removeObject(x, y);
				}
				xc = null;
			}
		}
	}
    
	public List<Player> getNearPlayers(int absX, int absY, byte heightLevel) {
		List<Player> findPlayers = new ArrayList<Player>();
		int minX = absX - 57;
		int maxX = absX + 57;
		int minY = absY - 57;
		int maxY = absY + 57;
		
		for (Player player : players) {
			if (player == null)
				continue;
			if (player.getAbsH() == heightLevel && isBetween(player.getAbsX(), minX, maxX) && isBetween(player.getAbsY(), minY, maxY))
				findPlayers.add(player);
		}
		return findPlayers;
	}
	
	private boolean isBetween(int value, int min, int max) {
		return value >= min && value <= max;
	}
	
	public void createGroundItemAll(int itemID, int x, int y, byte h, int count) {
		List<Player> players = getNearPlayers(x, y, h);
		for (Player player : players)
			((client)player).createGroundItem(itemID, x, y, count);
	}
	
	public void removeGroundItemAll(int itemID, int x, int y, byte h) {
		List<Player> players = getNearPlayers(x, y, h);
		for (Player player : players)
			((client)player).removeGroundItem(itemID, x, y);
	}
	
	public void replaceObjectAll(int objID, int x, int y, byte h, int Face, int ObjectType) {
		List<Player> players = getNearPlayers(x, y, h);
		for (Player player : players)
			((client)player).replaceObject(objID, x, y, Face, ObjectType);
	}
	
	public void removeObjectAll(int x, int y, byte h) {
		replaceObjectAll(-1, x, y, h, 0, 0);
	}
	
	public void removeObjectAll(int x, int y, byte h, int ObjectType) {
		replaceObjectAll(-1, x, y, h, 0, ObjectType);
	}
	
	public void resetOnlinePlayer () {
		try {
			DBService.getInstance().doExecute("UPDATE player_account SET online = 0");
		}
        catch (SQLException ex) {
        	LogService.getInstance().logError("Can't reset online player");
        	LogService.getInstance().logError(ex.getMessage());
        }
	}
	
	public int getClientCountByHost(String host) {
		int count = 0;
		for (Player player : players) {
			if (player != null) {
				if (((client)player).getSocket().getInetAddress().getHostAddress().equalsIgnoreCase(host))
					count++;
			}
		}
		return count;
	}
	
	public static PlayerHandler getInstance() { return instance; }
}
